#include "fxcc/core/graph/opengl3/Effect3d.h"
#include "fxcc/core/graph/opengl3/IBuffers.h"

using namespace fxcc::graph::opengl3;

fxcc::graph::opengl3::Effect3d::Effect3d(const fxcc::graph::common::EffectDesc& effectDesc)
	:m_EffectDesc(effectDesc)
{
	GetBuffers() = fxcc::graph::opengl3::RenderStates::GetBuffers();
	m_SimpleShaderDesc.m_FragCode = fxcc::FileUtil::ReadFileToString(effectDesc.m_FragPath);
	Init();
}


fxcc::graph::opengl3::Effect3d::Effect3d(const std::string& fragMain, const fxcc::graph::common::Defines defines)
{
	m_EffectDesc.m_Defines = defines;
	GetBuffers() = fxcc::graph::opengl3::RenderStates::GetBuffers();
	m_SimpleShaderDesc.m_FragCode = fragMain;
	Init();
}

fxcc::graph::opengl3::Effect3d::Effect3d(const ShaderDesc& shaderDesc, const fxcc::graph::common::Defines defines)
	:m_SimpleShaderDesc(shaderDesc)
{
	m_EffectDesc.m_Defines = defines;
	GetBuffers() = fxcc::graph::opengl3::RenderStates::GetBuffers();
	Init();
}
void fxcc::graph::opengl3::Effect3d::Influence(fxcc::graph::common::MaterialView& material)
{
	m_ShaderLang->InfluenceMaterial(material);
}

void fxcc::graph::opengl3::Effect3d::UseMaterial(const fxcc::graph::common::MaterialView& material)
{
	assert(m_CurrentShader);
	m_CurrentShader->UseMaterial(material);
}

void fxcc::graph::opengl3::Effect3d::Init()
{

	std::string fragCode = GetFragCode();

	{
		ShaderDesc& shaderDesc = m_FixedShaderDesc;
		shaderDesc.m_VertCode = Effect3d::GetVertCode();
		shaderDesc.m_FragCode = fragCode;

		if (m_SimpleShaderDesc.m_TessellationControlCode.size())
			shaderDesc.m_TessellationControlCode = GetTCSCode();
		if (m_SimpleShaderDesc.m_TessellationEvalutionCode.size())
			shaderDesc.m_TessellationEvalutionCode = GetTESCode();


		m_Shader = std::make_shared<Shader>(shaderDesc);
	}
	{
		ShaderDesc& shaderDesc = m_AnimShaderDesc;

		shaderDesc.m_VertCode = Effect3d::GetVertAnimCode();
		shaderDesc.m_FragCode = fragCode;

		if (m_SimpleShaderDesc.m_TessellationControlCode.size())
			shaderDesc.m_TessellationControlCode = GetTCSCode();
		if (m_SimpleShaderDesc.m_TessellationEvalutionCode.size())
			shaderDesc.m_TessellationEvalutionCode = GetTESCode();
		m_AnimShader = std::make_shared<Shader>(shaderDesc);
	}

	m_ShaderLang = std::make_shared<fxcc::graph::common::GlslShader>();
	m_ShaderLang->addFragShader(GetFragCode());
	m_ShaderLang->Build();

	m_Shader->Use();
	m_Shader->Bind("PassBuffer", 0);
	m_Shader->Bind("ObjBuffer", 1);
	m_Shader->Bind("BoneBuffer", 2);
	m_Shader->Bind("LightBuffer", 3);

	m_Shader->setInt("irradianceMap", 0);
	m_Shader->setInt("prefilterMap", 1);
	m_Shader->setInt("brdfLUT", 2);

	m_AnimShader->Use();
	m_AnimShader->Bind("PassBuffer", 0);
	m_AnimShader->Bind("ObjBuffer", 1);
	m_AnimShader->Bind("LightBuffer", 3);
	
	m_AnimShader->setInt("irradianceMap", 0);
	m_AnimShader->setInt("prefilterMap", 1);
	m_AnimShader->setInt("brdfLUT", 2);
	// default is the fixed shader
	m_CurrentShader = m_Shader;


}

void Effect3d::Begin()
{
	RenderStates::Inst().Clear(m_PipeLineType);
}

void Effect3d::SetPassData(const PassData& passData)
{
	GetBuffers()->SetPassData(passData);
}

void Effect3d::SetObjData(const ObjData& objData)
{
	GetBuffers()->SetObjData(objData);
}

void fxcc::graph::opengl3::Effect3d::SetObjData(const fxcc::graph::common::Transform& tranform)
{
	ObjData objData;
	objData.Load(tranform);
	SetObjData(objData);
}

std::shared_ptr<fxcc::graph::opengl3::IBuffers> fxcc::graph::opengl3::Effect3d::GetBuffers()
{
	auto& rs = fxcc::graph::opengl3::RenderStates::Inst();
	return rs.m_Buffers;
}

void Effect3d::SetBoneData(const BoneData& boneData)
{
	GetBuffers()->SetBoneData(boneData);

}

void fxcc::graph::opengl3::Effect3d::SetLightEffect(const fxcc::graph::common::LightEffect& lightEffect)
{
	GetBuffers()->SetLightEffectData(lightEffect);

}


void Effect3d::SetCamera(const fxcc::graph::common::Camera& camera)
{
	PassData passData;
	passData.Load(camera);
	SetPassData(passData);
}

void fxcc::graph::opengl3::Effect3d::SetCamera(const fxcc::graph::common::OrthoCamera& camera)
{
	PassData passData;
	passData.Load2(camera);
	SetPassData(passData);
}

void Effect3d::SetTransform(const fxcc::graph::common::Transform& transform)
{
	ObjData objData;
	objData.Load(transform);
	SetObjData(objData);
}

void fxcc::graph::opengl3::Effect3d::GetTexture2DJackBufferData(int width, int height, PassData& passData, ObjData& objData)
{
	fxcc::graph::common::OrthoCamera orthoCamera;
	fxcc::graph::common::Transform quadTransform;

	glm::vec2 m_Size;
	m_Size.x = width;
	m_Size.y = height;

	orthoCamera.SetPosition(glm::vec3(0.0f, 0.0f, 1.0f));
	orthoCamera.LookAt(glm::vec3(0.0f));
	orthoCamera.SetHalfExtends(glm::vec3(m_Size.x * 2, m_Size.y * 2, 10.0f));

	quadTransform.m_Scale = glm::vec3(m_Size.x * 2.0f, m_Size.y * 2.0f, 1.f);

	passData.Load2(orthoCamera);
	objData.Load(quadTransform);
}

void fxcc::graph::opengl3::Effect3d::GetTexture2DJackBufferData(const fxcc::graph::common::ImageMipmap::MipMapData& desc, PassData& passData, ObjData& objData)
{
	fxcc::graph::common::OrthoCamera orthoCamera;
	fxcc::graph::common::Transform quadTransform;

	glm::vec2 m_Size;
	m_Size.x = desc.m_Width;
	m_Size.y = desc.m_Height;

	orthoCamera.SetPosition(glm::vec3(0.0f, 0.0f, 1.0f));
	orthoCamera.LookAt(glm::vec3(0.0f));
	orthoCamera.SetHalfExtends(glm::vec3(m_Size.x * 2, m_Size.y * 2, 10.0f));

	quadTransform.m_Scale = glm::vec3(m_Size.x * 2.0f, m_Size.y * 2.0f, 1.f);

	passData.Load2(orthoCamera);
	objData.Load(quadTransform);

}

void fxcc::graph::opengl3::Effect3d::InstQuad()
{
	m_Shader->Use();

	GetBuffers()->m_QuadMesh->Bind();
	GetBuffers()->m_QuadMesh->DrawElementsAuto();
}

void fxcc::graph::opengl3::Effect3d::InstQuad2()
{
	m_Shader->Use();
	GetBuffers()->m_Quad2Mesh->Bind();
	GetBuffers()->m_Quad2Mesh->DrawElementsAuto();
}

void fxcc::graph::opengl3::Effect3d::SetSkinned()
{
	m_CurrentShader = m_AnimShader;
	m_CurrentShader->Use();
}

void fxcc::graph::opengl3::Effect3d::SetFixed()
{
	m_CurrentShader = m_Shader;
	m_CurrentShader->Use();
}


void fxcc::graph::opengl3::Effect3d::InstMeshJack(const fxcc::graph::opengl3::MeshJack* mesh)
{
	m_Shader->Use();
	mesh->Bind();
	mesh->DrawElementsAuto();
}

void fxcc::graph::opengl3::Effect3d::InstSkinnedMeshJack(const fxcc::graph::opengl3::MeshJack* mesh)
{
	m_AnimShader->Use();
	mesh->Bind();
	mesh->DrawElementsAuto();
}



void Effect3d::End()
{
	Shader::UnUse();
}

const std::string Effect3d::GetVertAnimCode()
{

	kainjow::mustache::mustache m1(GetVertTemp());

	kainjow::mustache::data d1;
	d1.set("version", fxcc::graph::opengl3::IBuffers::GetVersionStr());
	d1.set("uniformPart", fxcc::graph::opengl3::IBuffers::GetUniformPart3d());
	
	kainjow::mustache::list l1;
	const auto& defines1 = GetSkinnedDefine();
	defines1.Effect(l1);
	//m_Defines.Effect(l1);
	d1.set("defines", l1);
	
	return m1.render(d1);
}

const std::string Effect3d::GetVertCode()
{

	kainjow::mustache::mustache m1(GetVertTemp());

	kainjow::mustache::data d1;
	d1.set("version", fxcc::graph::opengl3::IBuffers::GetVersionStr());
	d1.set("uniformPart", fxcc::graph::opengl3::IBuffers::GetUniformPart3d());
	kainjow::mustache::list l1;
	d1.set("defines", l1);
	return m1.render(d1);
}

void fxcc::graph::opengl3::Effect3d::Viewport(int x, int y, int w, int h)
{
	glViewport(x, y, w, h);
}


void fxcc::graph::opengl3::Effect3d::ClearBgColor(const glm::vec4& v)
{
	glClearColor(v.x, v.y, v.z, v.w);
}

void fxcc::graph::opengl3::Effect3d::ClearBuffer(bool color, bool depthStencil)
{
	int flag{ 0 };
	if (color) {
		flag |= GL_COLOR_BUFFER_BIT;

	}
	if (depthStencil)
	{
		flag |= (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	}
	glClear(flag);
}


const std::string Effect3d::GetVertTemp()
{


	return R"(

{{{version}}}

{{#defines}}
#define {{{key}}} {{{value}}}
{{/defines}}

layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;
layout(location=2) in vec2 aTexCoord;
layout(location=3) in vec3 aTangent;
layout(location=4) in vec3 aBigTangent;

#ifdef _SKINNED_VERT_
layout(location=5) in ivec4 aBones1;
layout(location=6) in vec4 aWeights1;
layout(location=7) in ivec4 aBones2;
layout(location=8) in vec4 aWeights2;

#endif

{{{uniformPart}}}

out VertexOut vOut;

#ifdef _SKINNED_VERT_

mat4 GetBoneMatrix() {
	mat4 bMat = mat4(0.0f);

	bMat += boneData.boneData[aBones1[0]] * aWeights1[0];
	bMat += boneData.boneData[aBones1[1]] * aWeights1[1];
	bMat += boneData.boneData[aBones1[2]] * aWeights1[2];
	bMat += boneData.boneData[aBones1[3]] * aWeights1[3];
	bMat += boneData.boneData[aBones2[0]] * aWeights2[0];
	bMat += boneData.boneData[aBones2[1]] * aWeights2[1];
	bMat += boneData.boneData[aBones2[2]] * aWeights2[2];
	bMat += boneData.boneData[aBones2[3]] * aWeights2[3];
	return bMat;
};
#endif


void CalVertexOut(out VertexOut vOut)
{

#ifdef _SKINNED_VERT_

	mat4 boneMat = GetBoneMatrix();
	mat4 FragMat = objData.world * boneMat;
#else
	mat4 FragMat = objData.world ;
#endif
	vOut.NativePos = aPos;
    vec4 FragPosAlpha =FragMat * vec4(aPos,1.0f);
	vOut.FragPos = FragPosAlpha.xyz ;
	vOut.ViewFragPos= passData.view* FragPosAlpha;
    vOut.Position= passData.proj* vOut.ViewFragPos;;

	vOut.Normal = normalize(transpose(inverse(mat3(FragMat))) * aNormal);
	vOut.ViewNormal =transpose(inverse(mat3(passData.view * FragMat)))* aNormal;
	vOut.TexCoords = aTexCoord;

};

void main()
{
	CalVertexOut(vOut);
	gl_Position= vOut.Position;

};
)";
}

const std::string Effect3d::GetFragCodeTemp() {

	return R"(
{{{version}}}

{{#defines}}
#define {{{key}}} {{{value}}}
{{/defines}}

{{{uniformPart}}}

in VertexOut vOut;

{{{FragPart}}}
)";
}

std::string fxcc::graph::opengl3::Effect3d::GetTCSCode()
{

	static const std::string tempStr = R"(

{{{version}}}
{{{uniformPart}}}


{{{tcsCode}}}
)";
	kainjow::mustache::mustache m1(
		tempStr
	);

	kainjow::mustache::data d1;
	d1.set("version", fxcc::graph::opengl3::IBuffers::GetVersionStr());
	d1.set("uniformPart", fxcc::graph::opengl3::IBuffers::GetUniformPart3d());

	d1.set("tcsCode", m_SimpleShaderDesc.m_TessellationControlCode);
	return m1.render(d1);
}


std::string fxcc::graph::opengl3::Effect3d::GetTESCode()
{

	static const std::string tempStr = R"(

{{{version}}}
{{{uniformPart}}}

{{{tesCode}}}
)";
	kainjow::mustache::mustache m1(
		tempStr
	);

	kainjow::mustache::data d1;
	d1.set("version", fxcc::graph::opengl3::IBuffers::GetVersionStr());
	d1.set("uniformPart", fxcc::graph::opengl3::IBuffers::GetUniformPart3d());
	d1.set("tesCode", m_SimpleShaderDesc.m_TessellationEvalutionCode);

	return m1.render(d1);
}

std::string Effect3d::GetFragCode()
{

	kainjow::mustache::mustache m1 = GetFragCodeTemp();

	kainjow::mustache::data d1;
	d1.set("FragPart", this->m_SimpleShaderDesc.m_FragCode);
	d1.set("version", fxcc::graph::opengl3::IBuffers::GetVersionStr());
	d1.set("uniformPart", fxcc::graph::opengl3::IBuffers::GetUniformPart3d());

	kainjow::mustache::list l1;
	m_EffectDesc.m_Defines.Effect(l1);
	d1.set("defines", l1);

	return m1.render(d1);
}


fxcc::graph::common::Defines fxcc::graph::opengl3::Effect3d::GetSkinnedDefine()
{
	fxcc::graph::common::Defines defines;
	defines.Insert("_SKINNED_VERT_", "");
	return defines;
}


